---
title: Send notifications with FCM legacy server
hideFromSearch: true
description: Learn how to send notifications with FCM legacy server.
---

> **warning** This page is archived. See [Push Notifications guide](/push-notifications/overview/) for up-to-date information.

> **info** For documentation on communicating with the newer FCMv1 service, see [Send notifications with FCMv1 and APNs](/push-notifications/sending-notifications-custom/). This guide is based on [Google's documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref), and this section covers the basics to get you started.
>
> Before sending a notification directly through FCM, you will need to [obtain a device token](/push-notifications/obtaining-a-device-token-for-fcm-or-apns).

Communicating with FCM is done by sending a POST request. However, before sending or receiving any notifications, you'll need to follow the steps to [configure FCM](/push-notifications/push-notifications-setup/#android) to configure FCM and get your `FCM-SERVER-KEY`.

```js
await fetch('https://fcm.googleapis.com/fcm/send', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `key=<FCM-SERVER-KEY>`,
  },
  body: JSON.stringify({
    to: '<NATIVE-DEVICE-PUSH-TOKEN>',
    priority: 'normal',
    data: {
      experienceId: '@yourExpoUsername/yourProjectSlug',
      scopeKey: '@yourExpoUsername/yourProjectSlug',
      title: "\uD83D\uDCE7 You've got mail",
      message: 'Hello world! \uD83C\uDF10',
    },
  }),
});
```

**The `experienceId` and `scopeKey` fields are required**. Otherwise, your notifications will not go through to your app. FCM has a list of supported fields in the [notification payload](https://firebase.google.com/docs/cloud-messaging/http-server-ref#notification-payload-support), and you can see which ones are supported by `expo-notifications` on Android by looking at the [FirebaseRemoteMessage](/versions/latest/sdk/notifications/#firebaseremotemessage).

FCM also provides some [server-side libraries in a few different languages](https://firebase.google.com/docs/cloud-messaging/send-message#node.js) you can use instead of raw `fetch` requests.

### How to find FCM server key

Your FCM server key can be found by making sure you've followed the [configuration steps](/push-notifications/push-notifications-setup/#android), and instead of uploading your FCM key to Expo, you would use that key directly in your server (as the `FCM-SERVER-KEY` in the previous example).

## Payload format

```json
{
  "token": native device token string,
  "collapse_key": string that identifies notification as collapsible,
  "priority": "normal" || "high",
  "data": {
    "experienceId": "@yourExpoUsername/yourProjectSlug",
    "scopeKey": "@yourExpoUsername/yourProjectSlug",
    "title": title of your message,
    "message": body of your message,
    "channelId": the android channel ID associated with this notification,
    "categoryId": the category associated with this notification,
    "icon": the icon to show with this notification,
    "link": the link this notification should open,
    "sound": boolean or the custom sound file you'd like to play,
    "vibrate": "true" | "false" | number[],
    "priority": AndroidNotificationPriority, // https://docs.expo.dev/versions/latest/sdk/notifications/#androidnotificationpriority
    "badge": the number to set the icon badge to,
    "body": { object of key-value pairs }
  }
}
```

### Firebase notification types

There are two types of Firebase Cloud Messaging messages: [notification and data messages](https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages).

1. **Notification** messages are only handled (and displayed) by the Firebase library. They don't necessarily wake the app, and `expo-notifications` will not be made aware that your app has received any notification.

2. **Data** messages are not handled by the Firebase library. They are immediately handed off to your app for processing. That's where `expo-notifications` interprets the data payload and takes action based on that data. **In almost all cases, this is the type of notification you have to send.**

If you send a message of type **notification** instead of **data** directly through Firebase, you won't know if a user interacted with the notification (no `onNotificationResponse` event available), and you won't be able to parse the notification payload for any data in your notification event-related listeners.

> Using notification-type messages can be beneficial when you need a configuration option that is not yet exposed by `expo-notifications`. Generally, it may lead to less predictable situations than using data-type messages. However, you may need to report any issue you encounter directly to Google.

Below is an example of each type using Node.js Firebase Admin SDK to send data-type messages instead of notification-type:

```js
const devicePushToken = /* ... */;
const options = /* ... */;

// ❌ The following payload has a root-level notification object and
// it will not trigger expo-notifications and may not work as expected.
admin.messaging().sendToDevice(
  devicePushToken,
  {
    notification: {
      title: "This is a notification-type message",
      body: "`expo-notifications` will never see this 😢",
    },
    data: {
      photoId: 42,
    },
  },
  options
);

// ✅ There is no "notification" key in the root level of the payload
// so the message is a "data" message, thus triggering expo-notifications.
admin.messaging().sendToDevice(
  devicePushToken,
  {
    data: {
      title: "This is a data-type message",
      message: "`expo-notifications` events will be triggered 🤗",
      // ⚠️ Notice the schema of this payload is different
      // than that of Firebase SDK. What is there called "body"
      // here is a "message". For more info see:
      // https://docs.expo.dev/versions/latest/sdk/notifications/#android-push-notification-payload-specification

      body:                              // As per Android payload format specified above, the
        JSON.stringify({ photoId: 42 }), // additional "data" should be placed under "body" key.
    },
  },
  options
);
```
